//
// Created by antler on 2018/12/6.
//

#include "Screen.h"

#include "GlobalMacros.h"

#include "ScreenUtils.h"

#include "luaext/utils/StringUtils.h"

Screen *Screen::g_pScreen = nullptr;

Screen *Screen::getInstance() {
    if (!g_pScreen) {
        g_pScreen = new(std::nothrow)Screen();
    }
    return g_pScreen;
}

void Screen::releaseInstance() {
    SAFE_DELETE(g_pScreen);
}

Screen::Screen()
        : mOrientation(LANDSCAPE_RIGHT), mbKeepCapture(false), mScreenCapture(nullptr),
          mMockMode(MOCK_NONE) {
}

Screen::~Screen() {
    SAFE_DELETE(mScreenCapture);
}

void Screen::init(int orientation) {
    if (orientation == LANDSCAPE_RIGHT ||
        orientation == LANDSCAPE_LEFT ||
        orientation == PORTRAIT) {
        mOrientation = orientation;
    }
}

Size Screen::getSize() {
    return ScreenUtils::getSize();
}

int Screen::getDPI() {
    return ScreenUtils::getDPI();
}

Image *Screen::capture(const Rect &rect) {
    return Image::fromScreen(rect);
}

void Screen::keep(bool value) {
    mbKeepCapture = value;
    if (mbKeepCapture) {
        captureScreen();
    } else {
        releaseScreenCapture();
    }
}

bool Screen::isKeepScreenCapture() const {
    return mbKeepCapture;
}

int Screen::getOrientation() {
    return mOrientation;
}

bool Screen::snapshot(const std::string &filePath, const Rect &rect, int quality) {
    bool ret = false;

    do {
        Image *image = Image::fromScreen(rect);
        if (!image) {
            break;
        }

        ret = image->saveToFile(filePath, quality);
        SAFE_DELETE(image);
    } while (0);
    return ret;
}

bool Screen::getColor(const Point &pos, Color3B &c3b) {
    bool ret = false;

    do {
        if (!takeScreenCapture()) {
            break;
        }

        ret = mScreenCapture->getColor(pos, c3b);
    } while (0);
    return ret;
}

// "x|y|color-offset|fuzz,x|y|color-offset|fuzz,..."
static void ColorStringList2ColorList(const std::string &colors, Screen::COLOR_LIST &colorList) {
    std::vector<std::string> colorItemStringList;
    StringUtils::split(colors, ",", colorItemStringList);
    for (const auto &itemList : colorItemStringList) {
        std::vector<std::string> colorItemString;
        StringUtils::split(itemList, "|", colorItemString);

        int x = -1;
        int y = -1;
        int color = COLOR_INVALID;
        int offset = COLOR_INVALID;
        int fuzz = -1;

        int size = colorItemString.size();
        if (size < 3) {
            colorList.clear();
            break;
        }

        x = StringUtils::stoi(colorItemString[0], -1);
        y = StringUtils::stoi(colorItemString[1], -1);

        std::vector<std::string> colorAndOffset;
        StringUtils::split(colorItemString[2], "-", colorAndOffset);

        color = StringUtils::stoi(colorAndOffset[0], COLOR_INVALID);
        if (colorAndOffset.size() > 1) {
            offset = StringUtils::stoi(colorAndOffset[1], COLOR_INVALID);
        }

        if (size > 3) {
            fuzz = StringUtils::stoi(colorItemString[3], -1);
        }

        if (color == COLOR_INVALID) {
            colorList.clear();
            break;
        }

        colorList.push_back({x, y, color, offset, fuzz});
    }
}

bool Screen::matchColors(const std::string &colors, int globalFuzz) {
    LOGV("Screen::matchColors colors:%s, globalFuzz:%d", colors.c_str(), globalFuzz);

    bool bMatchColors = false;

    do {
        COLOR_LIST colorList;
        ColorStringList2ColorList(colors, colorList);

        if (colorList.empty()) {
            break;
        }

        if (!takeScreenCapture()) {
            break;
        }

        int matchCount = 0;

        void *addrPtr = mScreenCapture->lockPixels();
        if (addrPtr) {
            for (const auto &v :colorList) {
                Color3B c3b;
                if (mScreenCapture->getPixel(addrPtr, v.x, v.y, c3b)) {
//                    LOGV("Screen::matchColors [%d,%d], c3b:0x%08x, v.color:0x%08x, v.offset:0x%08x, v.fuzz：%d, globalFuzz:%d",
//                         v.x, v.y, c3b.toInt(), v.color, v.offset, v.fuzz, globalFuzz);

                    if (c3b.isEqual(v.color, v.offset, v.fuzz == -1 ? globalFuzz : v.fuzz)) {
                        ++matchCount;
                    } else {
                        break;
                    }
                } else {
                    break;
                }
            }
            mScreenCapture->unlockPixels();
        }

        if (matchCount != colorList.size()) {
            break;
        }

        bMatchColors = true;

    } while (0);

    return bMatchColors;
}

Point Screen::findImage(const Rect &rect, const std::string &path, int fuzziness, int priority,
                        int ignoreColor) {
    return Point();
}

Point Screen::findImage(const Rect &rect, const Image &image, int fuzziness, int priority,
                        int ignoreColor) {
    return Point();
}

static bool FindColors(const Color3B &color, const Screen::COLOR_LIST &colorList) {


    return false;
}

Screen::POINT_LIST
Screen::findColors(const Rect &rect, const std::string &colors, int globalFuzz, int priority,
                   int limit) {
    LOGV("Screen::findColors rect:[%d,%d,%d,%d], colors:%s, globalFuzz:%d, priority:0x%0x, limit:%d",
         rect.x, rect.y, rect.width, rect.height, colors.c_str(),
         globalFuzz,
         priority, limit);

    Screen::POINT_LIST pointList;

    do {
        COLOR_LIST colorList;
        ColorStringList2ColorList(colors, colorList);

        if (colorList.empty()) {
            break;
        }

        if (!takeScreenCapture()) {
            break;
        }

        void *addrPtr = mScreenCapture->lockPixels();
        if (addrPtr) {
            Size size = mScreenCapture->getSize();

            const int LEFT = std::max(0, rect.x);
            const int RIGHT = std::min(rect.x + rect.width, size.width);
            const int UP = std::max(0, rect.y);
            const int BOTTOM = std::min(rect.y + rect.height, size.height);

            // 屏蔽位如果不够，则补位
            if ((priority & PRIORITY_LEFT_FIRST) == 0) {
                priority |= PRIORITY_LEFT_FIRST;
            }

            if ((priority & PRIORITY_UP_FIRST) == 0) {
                priority |= PRIORITY_UP_FIRST;
            }

            if ((priority & PRIORITY_HORIZONTAL_FIRST) == 0) {
                priority |= PRIORITY_HORIZONTAL_FIRST;
            }

            switch (priority) {
                case PRIORITY_LEFT_FIRST | PRIORITY_UP_FIRST | PRIORITY_HORIZONTAL_FIRST: {
                    for (int y = UP; y < BOTTOM; ++y) {
                        for (int x = LEFT; x < RIGHT; ++x) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_LEFT_FIRST | PRIORITY_UP_FIRST | PRIORITY_VERTICAL_FIRST: {
                    for (int x = LEFT; x < RIGHT; ++x) {
                        for (int y = UP; y < BOTTOM; ++y) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_LEFT_FIRST | PRIORITY_DOWN_FIRST | PRIORITY_HORIZONTAL_FIRST: {
                    for (int y = BOTTOM - 1; y >= UP; --y) {
                        for (int x = LEFT; x < RIGHT; ++x) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_LEFT_FIRST | PRIORITY_DOWN_FIRST | PRIORITY_VERTICAL_FIRST: {
                    for (int x = LEFT; x < RIGHT; ++x) {
                        for (int y = BOTTOM - 1; y >= UP; --y) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_RIGHT_FIRST | PRIORITY_UP_FIRST | PRIORITY_HORIZONTAL_FIRST: {
                    for (int y = UP; y < BOTTOM; ++y) {
                        for (int x = RIGHT - 1; x >= LEFT; --x) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_RIGHT_FIRST | PRIORITY_UP_FIRST | PRIORITY_VERTICAL_FIRST: {
                    for (int x = RIGHT - 1; x >= LEFT; --x) {
                        for (int y = UP; y < BOTTOM; ++y) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_RIGHT_FIRST | PRIORITY_DOWN_FIRST | PRIORITY_HORIZONTAL_FIRST: {
                    for (int y = BOTTOM - 1; y >= UP; --y) {
                        for (int x = RIGHT - 1; x >= LEFT; --x) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
                case PRIORITY_RIGHT_FIRST | PRIORITY_DOWN_FIRST | PRIORITY_VERTICAL_FIRST: {
                    for (int x = RIGHT - 1; x >= LEFT; --x) {
                        for (int y = BOTTOM - 1; y >= UP; --y) {
                            if (checkColor(addrPtr, x, y, colorList, globalFuzz)) {
                                pointList.push_back({x, y});
                                if (pointList.size() >= limit) {
                                    goto __END_;
                                }
                            }
                        }
                    }
                    break;
                }
            }

            __END_:

            mScreenCapture->unlockPixels();
        }

    } while (0);

    return pointList;
}

bool Screen::checkColor(void *addrPtr, int x, int y, const Screen::COLOR_LIST &colorList,
                        int globalFuzz) {
    assert(addrPtr);

    int matchCount = 0;

    for (int i = 0; i < colorList.size(); ++i) {
        const ColorItem &v = colorList[i];

        int new_x = x + (v.x - colorList[0].x);
        int new_y = y + (v.y - colorList[0].y);

        Color3B c3b;
        if (mScreenCapture->getPixel(addrPtr, new_x, new_y, c3b)) {
//            LOGV("Screen::checkColor [%d,%d], c3b:0x%08x, v.color:0x%08x, v.offset:0x%08x, v.fuzz：%d, globalFuzz:%d",
//                 new_x, new_y, c3b.toInt(), v.color, v.offset, v.fuzz, globalFuzz);

            if (c3b.isEqual(v.color, v.offset, v.fuzz == -1 ? globalFuzz : v.fuzz)) {
                ++matchCount;
            } else {
                break;
            }
        } else {
            break;
        }
    }

    return (matchCount == colorList.size());
}

void Screen::setMockMode(int mode) {
    mMockMode = mode;
}

void Screen::setMockTransform() {

}

bool Screen::captureScreen() {
    releaseScreenCapture();
    mScreenCapture = Image::fromScreen();
    return (mScreenCapture != nullptr);
}

void Screen::releaseScreenCapture() {
    SAFE_DELETE(mScreenCapture);
}

bool Screen::takeScreenCapture() {
    bool ret = false;

    do {
        if (!mbKeepCapture) {
            captureScreen();
        }

        if (!isScreenCapture()) {
            break;
        }

        ret = true;
    } while (0);

    return ret;
}

bool Screen::isScreenCapture() {
    return (mScreenCapture != nullptr);
}

